/*
 * Segue Audio Player
 * Copyright 2011 Fraser McCrossan frasermccrossan@gmail.com
 * 
 * This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published
 *  by the Free Software Foundation, either version 3 of the License,
 *  or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.

 */

package com.frasermccrossan.segue;

import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.BaseColumns;
import android.provider.MediaStore;
import android.provider.MediaStore.Audio.AudioColumns;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;

import com.frasermccrossan.segue.QueuePlayer.PlayProgress;
import com.frasermccrossan.segue.providers.QueueProvider;

public class PlayingScreen extends FragmentActivity
	implements OnClickListener, OnLongClickListener, OnItemClickListener, SeekBar.OnSeekBarChangeListener,
		LoaderManager.LoaderCallbacks<Cursor> {

	public static final String TAG = "PlayingScreen";

	// flash timers when paused
	private static final int FLASH_CYCLE_TIME = 1500; // total length of flash cycle
	private static final int FLASH_ON_TIME = 1000; // how long in the cycle that timers are on
	private static final int FLASH_SLEEP_TIME = 500; // should be the greatest common divisor of above
	
	// context menu items
	private static final int REMOVE_TRACK = 0;
	private static final int REMOVE_ALL_BY_ARTIST = 1;
	private static final int REMOVE_ALL_IN_ALBUM = 2;
	private static final int SKIP_FORWARD = 3;
	private static final int SKIP_BACKWARD = 4;
	private static final int PLAY_NEXT = 5;
	private static final int PLAY_LATER = 6;
	private static final int RESUME_LATER = 7;
	
	private static final int PERCENT_FORWARD = 20;
	private static final int PERCENT_BACKWARD = 20;
	private static final int SECONDS_FORWARD = 15;
	private static final int SECONDS_BACKWARD = 5;

	// how long to show extended controls without activity
	private static final long EXTENDED_CONTROL_TIMEOUT = 10000;

	Toast mToast;

	ImageButton mPlayButton;
	ImageButton mAddButton;
	int lastPlayButtonImage = 0;
	ImageButton mForwardSkipButton;
	ImageButton mBackwardSkipButton;
	Button mForwardButton;
	Button mBackwardButton;
	
	long controlTimeoutTime = 0;
	
	TextView mElapsedTime;
	TextView mRemainingTime;
	SeekBar mTrackProgressBar;
	ViewGroup extendedControls;

	SimpleCursorAdapter mAdapter;
	
	ListView queueView;
	Cursor queueCursor = null;
	
	QueuePlayer mQueuePlayer = null;
	boolean mIsBound;
	
	PlayingStatusTask statusTask = null;
	
	ContentResolver cr;
	
	public class PlayingScreenCallback {
		// service informs screen that playback should be monitored
		public void startMonitoring() {
			startStatusTask();
		}
		// playback should no longer be monitored
		public void stopMonitoring() {
			stopStatusTask();
		}
	}
	
	PlayingScreenCallback callback = null;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        cr = getContentResolver();
        mToast = Toast.makeText(PlayingScreen.this, "", Toast.LENGTH_SHORT);
        mToast.setGravity(Gravity.TOP, 0, 0);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.play_queue);
        mPlayButton = (ImageButton)findViewById(R.id.play_button);
        mAddButton = (ImageButton)findViewById(R.id.add_button);
        mPlayButton.setOnClickListener(this);
        mAddButton.setOnClickListener(this);
        mPlayButton.setEnabled(false);
        
        mForwardSkipButton = (ImageButton)findViewById(R.id.forward_skip_button);
        mBackwardSkipButton = (ImageButton)findViewById(R.id.backward_skip_button);
        mForwardButton = (Button)findViewById(R.id.forward_button);
        mBackwardButton = (Button)findViewById(R.id.backward_button);
        mForwardSkipButton.setOnClickListener(this);
        mBackwardSkipButton.setOnClickListener(this);
        mForwardSkipButton.setOnLongClickListener(this);
        mBackwardSkipButton.setOnLongClickListener(this);
        mForwardButton.setOnClickListener(this);
        mBackwardButton.setOnClickListener(this);
        mForwardButton.setOnLongClickListener(this);
        mBackwardButton.setOnLongClickListener(this);
        
        String percent_seconds = getString(R.string.percent_seconds);
        String seconds_percent = getString(R.string.seconds_percent);
//        mForwardSkipButton.setText(String.format(percent, PERCENT_FORWARD));
//        mBackwardSkipButton.setText(String.format(percent, PERCENT_BACKWARD));
        mForwardButton.setText(String.format(seconds_percent, SECONDS_FORWARD, PERCENT_FORWARD));
        mBackwardButton.setText(String.format(percent_seconds, SECONDS_BACKWARD, PERCENT_BACKWARD));
        
        mElapsedTime = (TextView)findViewById(R.id.elapsed_time);
        mRemainingTime = (TextView)findViewById(R.id.remaining_time);
        mTrackProgressBar = (SeekBar)findViewById(R.id.track_progress_bar);
        mTrackProgressBar.setOnSeekBarChangeListener(this);
        setTrackControlsEnabled(false);
        
        extendedControls = (ViewGroup)findViewById(R.id.extended_controls);
        extendedControls.setClickable(true);
        extendedControls.setEnabled(true);
        extendedControls.setFocusableInTouchMode(true);
        extendedControls.setOnClickListener(this);
        
        // fetch the repo in the background
        //new GetRepoTask().execute();
        
        callback = new PlayingScreenCallback();
        queueView = (ListView)findViewById(R.id.track_list);
        queueView.setOnItemClickListener(this);
        registerForContextMenu(queueView);
        mAdapter = new SimpleCursorAdapter(this,
                R.layout.queued_item, null,
                new String[] { QueueProvider.ORDER, AudioColumns.TITLE, AudioColumns.ARTIST, AudioColumns.ALBUM },
                new int[] { R.id.queued_position, R.id.queued_title, R.id.queued_artist },
                0);
        mAdapter.setViewBinder(new BlankZeroOrderBinder());
        queueView.setAdapter(mAdapter);
        startService(new Intent(this, QueuePlayer.class));
        getSupportLoaderManager().initLoader(0, null, this);
    }
    
    private class BlankZeroOrderBinder implements SimpleCursorAdapter.ViewBinder {
    	
    	public boolean setViewValue(View view, Cursor cursor, int columnIndex) {
    		// if the order field is zero, show a play symbol instead
    		if (columnIndex == 1) {
    			TextView tv = (TextView) view;
    			if (cursor.getInt(columnIndex) == 0) {
    				tv.setText("");
    				tv.setBackgroundResource(android.R.drawable.ic_media_play);
    				return true;
    			}
    			else {
    				tv.setBackgroundResource(0);
    			}
    		}
    		return false;
    	}
    }
    
    private class PlayingStatusTask extends AsyncTask<Void, QueuePlayer.PlayProgress, Void> {
    	
    	protected Void doInBackground(Void... v) {
    		
    		PlayProgress progress;
    		
    		do {
    			if (mQueuePlayer == null) {
    				// do nothing if there's no service
    				try {
    					Thread.sleep(1000);
    				}
    				catch (InterruptedException e) {
    					// don't care, do nothing
    				}
    			}
    			else {
    				progress = mQueuePlayer.getProgress();
    				if (updateSeekBar) {
    					publishProgress(progress);
    				}
    				// compute the number of milliseconds until the next exact second
    				// in the playing track, and sleep that long - this let's us save a bit of CPU
    				long remainingPlaySecond;
    				long remainingRemainSecond;
    				if (progress.playing) {
    					remainingPlaySecond = 1000 - progress.progress % 1000;
    					remainingRemainSecond = progress.remaining % 1000;
        				try {
        					Thread.sleep(remainingPlaySecond < remainingRemainSecond ? remainingPlaySecond : remainingRemainSecond);
        				}
        				catch (InterruptedException e) {
        					// don't care, do nothing
        				}
    				}
    				else {
    					remainingPlaySecond = System.currentTimeMillis() % FLASH_SLEEP_TIME;
        				try {
        					Thread.sleep(FLASH_SLEEP_TIME-remainingPlaySecond);
        				}
        				catch (InterruptedException e) {
        					// don't care, do nothing
        				}

    				}
    			}
    		} while (!isCancelled());
    		return null;
    	}
    	
    	protected void onProgressUpdate(QueuePlayer.PlayProgress... progress) {
    		updateProgressFromProgress(progress[0]);
    	}
    	
    	protected void onPostExecute(Void v) {
    		// update the progress one more time; this is because even
    		// if the player is stopped it will return the progress saved in preferences
			updateProgress();
			mTrackProgressBar.setEnabled(false);
    	}
    }

    private void updateProgress() {
    	updateProgressFromProgress(mQueuePlayer.getProgress());
    }
    
    private void updateProgressFromProgress(QueuePlayer.PlayProgress progress) {
    	long now = System.currentTimeMillis();
    	if (getTrackControlsEnabled() && now > controlTimeoutTime) {
    		setTrackControlsEnabled(false);
    	}
		mElapsedTime.setText(progress.elapsedText);
		mRemainingTime.setText(progress.remainingText);
		mTrackProgressBar.setMax(progress.duration);
		mTrackProgressBar.setProgress(progress.progress);
		//mTrackProgressBar.setEnabled(progress.playing);
		int desiredPlayButtonImage;
		if (progress.playing) {
			mElapsedTime.setVisibility(View.VISIBLE);
			mRemainingTime.setVisibility(View.VISIBLE);
			desiredPlayButtonImage = R.drawable.pause_button;
		}
		else {
			int timers_on = (now % FLASH_CYCLE_TIME < FLASH_ON_TIME) ? View.VISIBLE : View.INVISIBLE;
			mElapsedTime.setVisibility(timers_on);
			mRemainingTime.setVisibility(timers_on);
			desiredPlayButtonImage = R.drawable.play_button;
		}
		if (lastPlayButtonImage != desiredPlayButtonImage) {
			mPlayButton.setImageResource(desiredPlayButtonImage);
			lastPlayButtonImage = desiredPlayButtonImage;
		}
    }
    
    private boolean getTrackControlsEnabled() {
    	return mTrackProgressBar.isEnabled();
    }
    
    private void setTrackControlsEnabled(boolean enabled) {
    	mTrackProgressBar.setEnabled(enabled);
    	//extendedControls.setVisibility(enabled ? View.VISIBLE : View.GONE);
    	mForwardSkipButton.setEnabled(enabled);
    	mBackwardSkipButton.setEnabled(enabled);
    	mForwardButton.setEnabled(enabled);
    	mBackwardButton.setEnabled(enabled);
    }
    
	
    private void proclaim(int textId) {
    	proclaim(getString(textId));
    }
    
	private void proclaim(CharSequence text) {
		mToast.setText(text);
		mToast.show();
	}
	
	private void resetControlTimeout() {
		controlTimeoutTime = System.currentTimeMillis() + EXTENDED_CONTROL_TIMEOUT;
	}

    private void toggleTrackControls() {
    	if (mQueuePlayer.isPlaying()) {
    		setTrackControlsEnabled(! getTrackControlsEnabled());
    	}
    	else {
    		proclaim(R.string.play_for_controls);
    		setTrackControlsEnabled(false);
    	}
    }
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            mQueuePlayer = ((QueuePlayer.QueueBinder)service).getService();
            mPlayButton.setEnabled(true);
        }

        public void onServiceDisconnected(ComponentName className) {
            mQueuePlayer = null;
        }
    };

    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created.  This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Uri baseUri = QueueProvider.CONTENT_URI;

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        return new CursorLoader(this, baseUri,
                				new String [] { BaseColumns._ID, QueueProvider.ORDER, AudioColumns.TITLE, AudioColumns.ARTIST, AudioColumns.ALBUM },
                				QueueProvider.ORDER + ">=?",
                				new String[] { QueueProvider.CURRENT_TRACK.toString() },
                				QueueProvider.ORDER);
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in.  (The framework will take care of closing the
        // old cursor once we return.)
    	try {
    		mAdapter.swapCursor(data);
    	}
    	catch (Exception e) {
    		Log.i(TAG, "onLoadFinished(): " + e.getMessage());    		
    	}
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed.  We need to make sure we are no
        // longer using it.
        mAdapter.swapCursor(null);
    }

    @Override
    public void onStart() {
    	super.onStart();
    	mIsBound = bindService(new Intent(this, QueuePlayer.class), mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    public void onResume() {
    	super.onResume();
    	startStatusTask();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.playing_screen_menu, menu);
        return true;
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle item selection
        switch (item.getItemId()) {
        case R.id.prefs:
            gotoPreferences();
            return true;
        case R.id.clear_queue:
        	mQueuePlayer.clearQueue();
        	return true;
        case R.id.shuffle:
        	mQueuePlayer.shuffleQueue();
        	queueView.smoothScrollToPosition(0); // jump back to the top of the list
        	return true;
        case R.id.playback_controls:
        	resetControlTimeout();
        	toggleTrackControls();
        	return true;
        case R.id.about:
        	startActivity(new Intent(PlayingScreen.this, About.class));
        	return true;
        case R.id.help:
        	startActivity(new Intent(PlayingScreen.this, Help.class));
        	return true;
        case R.id.exit:
        	stopStatusTask();
        	stopService(new Intent(this, QueuePlayer.class));
        	finish();
        	return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }
    
	@Override
    public void onClick(View clicked) {
		resetControlTimeout();
		if (clicked == mPlayButton) {
    		mQueuePlayer.playToggle();
    		// make sure UI updates immediately
    		if (! mQueuePlayer.isPlaying()) {
    			setTrackControlsEnabled(false);
    		}
    	}
    	else if (clicked == mAddButton) {
			startActivity(new Intent(PlayingScreen.this, BrowseLibrary.class));
    	}
    	else if (clicked == mForwardButton) {
			mQueuePlayer.seekSeconds(SECONDS_FORWARD);
    	}
    	else if (clicked == mForwardSkipButton) {
        	SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
			mQueuePlayer.playSkip(prefs.getBoolean("resume", false));
    	}
    	else if (clicked == mBackwardButton) {
			mQueuePlayer.seekSeconds(-SECONDS_BACKWARD);
    	}
    	else if (clicked == mBackwardSkipButton) {
        	SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
			mQueuePlayer.playSkipBack(prefs.getBoolean("resume", false));
    	}
    	else {
    		mQueuePlayer.proclaim("Fucked up!");
    	}
		updateProgress();
    }

    public boolean onLongClick(View clicked) {
		resetControlTimeout();
    	if (clicked == mBackwardButton) {
			mQueuePlayer.seekPercent(-PERCENT_BACKWARD);
			return true;
    	}
    	else if (clicked == mForwardSkipButton) {
        	SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
			mQueuePlayer.playSkip(! prefs.getBoolean("resume", false));
			return true;
    	}
    	else if (clicked == mForwardButton) {
			mQueuePlayer.seekPercent(PERCENT_FORWARD);
			return true;
    	}
    	else if (clicked == mBackwardSkipButton) {
        	SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
			mQueuePlayer.playSkipBack(! prefs.getBoolean("resume", false));
			return true;
    	}
    	return false;
    }
    
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
		if (parent == queueView) {
			if (position == 0) {
				resetControlTimeout();
				toggleTrackControls();
			}
			else {
				openContextMenu(view);
			}
		}
	}
	
	@Override
	public void onCreateContextMenu(ContextMenu menu, View v,
	                                ContextMenuInfo menuInfo) {
	  super.onCreateContextMenu(menu, v, menuInfo);
	  AdapterView.AdapterContextMenuInfo listItemInfo = (AdapterView.AdapterContextMenuInfo)menuInfo;
	  int item = listItemInfo.position;
	  Cursor c = cr.query(QueueProvider.CONTENT_URI,
			  new String[] { MediaStore.Audio.Media.TITLE },
			  QueueProvider.ORDER + "=?",
			  new String[] { Integer.toString(listItemInfo.position) },
			  null);
	  String title = "";
	  if (c != null) {
		  if (c.moveToFirst()) {
			  title = c.getString(0);
		  }
		  c.close();
	  }
	  if (item == QueueProvider.CURRENT_TRACK) {
		  menu.setHeaderTitle(String.format(getString(R.string.current_track), title));
		  menu.add(ContextMenu.NONE, PLAY_LATER, 30, getString(R.string.play_later));
		  menu.add(ContextMenu.NONE, RESUME_LATER, 30, getString(R.string.resume_later));
		  menu.add(ContextMenu.NONE, REMOVE_ALL_BY_ARTIST, 40, getString(R.string.remove_others_by));
		  menu.add(ContextMenu.NONE, REMOVE_ALL_IN_ALBUM, 50, getString(R.string.remove_others_in));
	  }
	  else {
		  menu.setHeaderTitle(String.format("%d. %s", item, title));
		  if (item > QueueProvider.NEXT_TRACK) {
			  menu.add(ContextMenu.NONE, PLAY_NEXT, 1, getString(R.string.play_next));
		  }
		  menu.add(ContextMenu.NONE, REMOVE_TRACK, 1, getString(R.string.remove_track));
		  menu.add(ContextMenu.NONE, REMOVE_ALL_BY_ARTIST, 2, getString(R.string.remove_all_by));
		  menu.add(ContextMenu.NONE, REMOVE_ALL_IN_ALBUM, 3, getString(R.string.remove_all_in));
	  }
	}
	
	@Override
	public boolean onContextItemSelected(MenuItem item) {
	  AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
	  switch (item.getItemId()) {
	  case REMOVE_TRACK:
	    mQueuePlayer.delTrack(info.position);
	    return true;
	  case REMOVE_ALL_BY_ARTIST:
		    mQueuePlayer.delByArtist(info.position);
		    return true;
	  case REMOVE_ALL_IN_ALBUM:
		    mQueuePlayer.delByAlbum(info.position);
		    return true;
	  case SKIP_FORWARD:
			mQueuePlayer.playSkip(false);
			return true;
	  case SKIP_BACKWARD:
			mQueuePlayer.playSkipBack(false);
			return true;
	  case PLAY_LATER:
			mQueuePlayer.playLater(info.position, false);
			return true;
	  case RESUME_LATER:
			mQueuePlayer.playLater(info.position, true);
			return true;
	  case PLAY_NEXT:
			mQueuePlayer.playNext(info.position);
			return true;
	  default:
	    return super.onContextItemSelected(item);
	  }
	}
	
	int lastSeekProgress;
	boolean updateSeekBar = true; // instructs the progress task to update the bar
	PlayProgress seekProgress;
	
	// handle the seek bar
	public void onProgressChanged(SeekBar seekbar, int progress, boolean fromUser) {
		if (fromUser) {
			lastSeekProgress = progress;
			seekProgress = mQueuePlayer.new PlayProgress(seekbar.getMax(), progress);
			updateProgressFromProgress(seekProgress);
		}
	}
	
	public void onStartTrackingTouch (SeekBar seekbar) {
		updateSeekBar = false;
	}
	
	public void onStopTrackingTouch (SeekBar seekbar) {
		mQueuePlayer.seekTo(lastSeekProgress);
		updateSeekBar = true;
		seekProgress = null; // make sure to drop the reference afterwards
	}
	
	void startStatusTask() {
		if (statusTask == null) {
			statusTask = new PlayingStatusTask();
			statusTask.execute();
		}
	}
	
	void stopStatusTask() {
		if (statusTask != null) {
			statusTask.cancel(true);
			statusTask = null;
		}
	}
		
    void gotoPreferences() {
    	Intent prefs = new Intent(PlayingScreen.this, MainPreferences.class);
    	startActivity(prefs);
    }
    
    @Override
    protected void onPause() {
    	stopStatusTask();
    	super.onPause();
    }
    
    @Override
    protected void onStop() {
        if (mIsBound) {
            // Detach our existing connection.
            unbindService(mConnection);
            mIsBound = false;
        }
    	super.onStop();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
    }

}